#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>
#include <stdint.h>

#define BUFFER_SIZE 1024          // 定义缓冲区大小
#define SERVER_PORT 6782          // 定义服务器端口

char folder_name[BUFFER_SIZE];    // 客户端文件夹名
int client_listen_port;           // 客户端监听端口

// 创建文件夹的函数
int create_folder(const char *name) {
    // 创建文件夹，如果文件夹已存在则忽略错误
    if (mkdir(name, 0755) == -1 && errno != EEXIST) {
        perror("创建文件夹失败");
        return 0;
    }
    return 1;
}

// 上传文件的函数
int upload_file(int sockfd, const char *filename) {
    char path[BUFFER_SIZE * 2];   // 构造文件路径
    snprintf(path, sizeof(path), "%s/%s", folder_name, filename);
    FILE *fp = fopen(path, "rb"); // 打开文件
    if (!fp) {
        perror("打开文件失败");
        return -1;
    }

    fseek(fp, 0, SEEK_END);       // 获取文件大小
    size_t size = ftell(fp);
    rewind(fp);

    uint32_t name_len = strlen(filename);  // 获取文件名长度和文件大小
    uint32_t file_size = size;

    send(sockfd, &name_len, sizeof(name_len), 0);  // 发送文件名长度
    send(sockfd, &file_size, sizeof(file_size), 0);  // 发送文件大小
    send(sockfd, filename, name_len, 0);  // 发送文件名

    char buf[BUFFER_SIZE];        // 缓冲区
    size_t n;
    while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)  // 循环读取文件并发送
        send(sockfd, buf, n, 0);

    fclose(fp);                   // 关闭文件
    printf("文件 %s 上传成功\n", filename);  // 打印上传成功信息
    return 0;
}

// 接收文件的函数
void receive_file(int sockfd) {
    uint32_t name_len, file_size;  // 文件名长度和文件大小
    ssize_t n = recv(sockfd, &name_len, sizeof(name_len), 0);  // 接收文件名长度
    if (n <= 0) return;

    n = recv(sockfd, &file_size, sizeof(file_size), 0);  // 接收文件大小
    if (n <= 0) return;

    char filename[256];           // 文件名
    n = recv(sockfd, filename, name_len, 0);  // 接收文件名
    if (n <= 0) return;
    filename[name_len] = '\0';    // 添加字符串结束符

    char path[BUFFER_SIZE * 2];   // 构造文件路径
    snprintf(path, sizeof(path), "%s/%s", folder_name, filename);

    FILE *fp = fopen(path, "wb"); // 打开文件用于写入
    if (!fp) {
        perror("保存文件失败");
        return;
    }

    char buf[BUFFER_SIZE];        // 缓冲区
    size_t total = 0;
    while (total < file_size) {   // 循环接收文件数据并写入文件
        n = recv(sockfd, buf, sizeof(buf), 0);
        if (n <= 0) break;
        fwrite(buf, 1, n, fp);
        total += n;
    }

    fclose(fp);                   // 关闭文件
    printf("接收到文件: %s (%u 字节)\n", filename, file_size);  // 打印接收文件信息
}

// 监听线程函数
void *listen_thread(void *arg) {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建监听套接字
    struct sockaddr_in addr = { .sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY, .sin_port = htons(client_listen_port) };  // 客户端地址结构体
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));  // 设置套接字选项，允许地址重用
    bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));  // 绑定地址
    listen(listenfd, 5);  // 开始监听

    printf("客户端正在监听端口 %d, 等待服务器连接...\n", client_listen_port);  // 打印监听信息

    while (1) {           // 循环接受服务器连接
        struct sockaddr_in srv;
        socklen_t len = sizeof(srv);
        int connfd = accept(listenfd, (struct sockaddr *)&srv, &len);  // 接受服务器连接
        if (connfd < 0) {
            perror("接受服务器连接失败");
            continue;
        }
        receive_file(connfd);  // 接收文件
        close(connfd);         // 关闭连接
    }
    return NULL;
}

// 列出文件夹中文件的函数
void list_files() {
    DIR *dir = opendir(folder_name);  // 打开文件夹
    if (!dir) {
        perror("打开文件夹失败");
        return;
    }
    struct dirent *ent;  // 目录项结构体指针
    printf("文件夹 %s 中的文件:\n", folder_name);
    while ((ent = readdir(dir)) != NULL)  // 循环读取目录项
        if (ent->d_type == DT_REG)        // 如果是普通文件
            printf("- %s\n", ent->d_name);
    closedir(dir);  // 关闭目录
}

// 编辑文件的函数
void edit_file(const char *filename) {
    char path[BUFFER_SIZE * 2];  // 构造文件路径
    snprintf(path, sizeof(path), "%s/%s", folder_name, filename);
    if (access(path, F_OK) != 0) {  // 检查文件是否存在
        printf("文件 %s 不存在\n", filename);
        return;
    }
    char cmd[BUFFER_SIZE * 4];     // 构造编辑命令
    snprintf(cmd, sizeof(cmd), "vim %s", path);
    system(cmd);                   // 执行编辑命令
}

// 删除文件的函数
void delete_file(const char *filename) {
    char path[BUFFER_SIZE * 2];  // 构造文件路径
    snprintf(path, sizeof(path), "%s/%s", folder_name, filename);
    if (remove(path) != 0)       // 删除文件
        perror("删除失败");
    else
        printf("已删除 %s\n", filename);
}

// 创建文件的函数
void create_file(const char *filename) {
    char path[BUFFER_SIZE * 2];  // 构造文件路径
    snprintf(path, sizeof(path), "%s/%s", folder_name, filename);
    if (access(path, F_OK) == 0) {  // 检查文件是否已存在
        printf("文件 %s 已存在\n", filename);
        return;
    }
    FILE *fp = fopen(path, "w");  // 打开文件用于写入
    if (!fp) {
        perror("创建失败");
        return;
    }
    fclose(fp);                   // 关闭文件
    char cmd[BUFFER_SIZE * 4];    // 构造编辑命令
    snprintf(cmd, sizeof(cmd), "vim %s", path);
    system(cmd);                  // 执行编辑命令
}

int main(int argc, char *argv[]) {
    if (argc != 4) {  // 检查命令行参数数量
        fprintf(stderr, "用法: %s <服务器IP> <文件夹名> <监听端口>\n", argv[0]);
        return 1;
    }

    strcpy(folder_name, argv[2]);  // 复制文件夹名
    client_listen_port = atoi(argv[3]);  // 获取客户端监听端口
    if (!create_folder(folder_name)) return 1;  // 创建文件夹

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建套接字
    struct sockaddr_in srv = { .sin_family = AF_INET, .sin_port = htons(SERVER_PORT) };  // 服务器地址结构体
    inet_pton(AF_INET, argv[1], &srv.sin_addr);  // 将IP地址从文本形式转换为二进制形式
    if (connect(sockfd, (struct sockaddr *)&srv, sizeof(srv)) < 0) {  // 连接服务器
        perror("连接服务器失败");
        close(sockfd);
        return 1;
    }

    printf("已连接到服务器 %s:%d\n", argv[1], SERVER_PORT);  // 打印连接信息

    char buf[BUFFER_SIZE*2];  // 构造发送给服务器的消息
    snprintf(buf, sizeof(buf), "%d\n%s\n", client_listen_port, folder_name);
    send(sockfd, buf, strlen(buf), 0);  // 发送消息给服务器

    pthread_t tid;  // 线程ID
    pthread_create(&tid, NULL, listen_thread, NULL);  // 创建监听线程
    pthread_detach(tid);  // 分离线程

    int choice;
    char filename[BUFFER_SIZE];
    while (1) {  // 主循环，处理用户输入
        printf("\n1. 上传文件\n2. 查看文件\n3. 修改文件\n4. 删除文件\n5. 创建文件\n6. 上传当前文件夹中所有文件\n7. 退出\n选择: ");  // 打印操作菜单
        if (scanf("%d", &choice) != 1) {  // 读取用户输入
            while (getchar() != '\n');
            continue;
        }
        switch (choice) {  // 根据用户选择执行相应操作
            case 1:
                printf("文件名: ");
                scanf("%s", filename);
                upload_file(sockfd, filename);  // 上传文件
                break;
            case 2:
                list_files();  // 列出文件夹中的文件
                break;
            case 3:
                printf("文件名: ");
                scanf("%s", filename);
                edit_file(filename);  // 编辑文件
                break;
            case 4:
                printf("文件名: ");
                scanf("%s", filename);
                delete_file(filename);  // 删除文件
                break;
            case 5:
                printf("文件名: ");
                scanf("%s", filename);
                create_file(filename);  // 创建文件
                break;
            case 6: {
                DIR *dir = opendir(folder_name);  // 打开文件夹
                if (!dir) {
                    perror("打开文件夹失败");
                    break;
                }
                struct dirent *ent;  // 目录项结构体指针
                while ((ent = readdir(dir)) != NULL) {  // 循环读取目录项
                    if (ent->d_type == DT_REG) {  // 如果是普通文件
                        printf("正在上传: %s\n", ent->d_name);
                        upload_file(sockfd, ent->d_name);  // 上传文件
                    }
                }
                closedir(dir);  // 关闭目录
                printf("所有文件上传完成。\n");
                break;
            }
            case 7:
                close(sockfd);  // 关闭套接字
                return 0;
            default:
                printf("无效选择\n");
        }
    }
}